/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.functors;

import org.jetbrains.annotations.NotNull;
import static com.softwarecraftsmen.functors.Boolean.False;
import static com.softwarecraftsmen.functors.Boolean.True;

public abstract class Optional<V>
{
	@NotNull
	public abstract V value();

	@NotNull
	public abstract Boolean hasSomething();

	public abstract void hasSomething(final @NotNull UnaryFunctor<V> hasSomethingBlock);

	@NotNull
	public abstract Boolean isNothing();

	public abstract void isNothing(final @NotNull NullaryFunctor isNothingBlock);

	public final void ifHasSomethingElseIfIsNothing(final @NotNull UnaryFunctor<V> hasSomethingBlock, final @NotNull NullaryFunctor isNothingBlock)
	{
		hasSomething(hasSomethingBlock);
		isNothing(isNothingBlock);
	}

	@NotNull
	public abstract <T> Optional<T> ifHasSomethingUseItElseReturnNone(final @NotNull UnaryReturningFunctor<V, T> hasSomethingBlock);
	@NotNull
	public static <V> None<V> None()
	{
		return new None<V>();
	}

	@NotNull
	public static <V> Something<V> Something(final @NotNull V value)
	{
		return new Something<V>(value);
	}

	public static final class None<V> extends Optional<V>
	{
		private None()
		{}

		@NotNull
		public V value()
		{
			throw new NullPointerException("There is no value");
		}

		@NotNull
		public Boolean hasSomething()
		{
			return False;
		}

		public void hasSomething(final @NotNull UnaryFunctor<V> hasSomethingBlock)
		{
		}

		@NotNull
		public Boolean isNothing()
		{
			return True;
		}

		public void isNothing(final @NotNull NullaryFunctor isNothingBlock)
		{
			isNothingBlock.closure();
		}

		@NotNull
		public <T> Optional<T> ifHasSomethingUseItElseReturnNone(final @NotNull UnaryReturningFunctor<V, T> hasSomethingBlock)
		{
			return None();
		}
	}

	public static final class Something<V> extends Optional<V>
	{
		private final V value;

		public Something(final @NotNull V value)
		{
			this.value = value;
		}

		@NotNull
		public V value()
		{
			return value;
		}

		@NotNull
		public Boolean hasSomething()
		{
			return True;
		}

		public void hasSomething(final @NotNull UnaryFunctor<V> hasSomethingBlock)
		{
			hasSomethingBlock.closure(value);
		}

		@NotNull
		public Boolean isNothing()
		{
			return False;
		}

		public void isNothing(final @NotNull NullaryFunctor isNothingBlock)
		{
		}

		@NotNull
		public <T> Optional<T> ifHasSomethingUseItElseReturnNone(final @NotNull UnaryReturningFunctor<V, T> hasSomethingBlock)
		{
			return Something(hasSomethingBlock.closure(value));
		}
	}
}
